import logging

import pymysql
from threading import RLock, current_thread


# def _transactional(func):
#     def _tran_call(db_manager, *args):
#         with db_manager:
#             result = func(*args)
#             return result
#
#     return _tran_call


class DBManager:
    def __init__(self, config=None):
        self.host = None
        self.user = None
        self.password = None
        self.db = None
        self.charset = 'utf8'
        self.port = 3306

        self._conns = {}
        self._conn_lock = RLock()
        self._depth = {}
        self._depth_lock = RLock()
        self.logger = logging.getLogger()
        if config is not None:
            self.set_config(config)

    def set_config(self, config):
        self.set_single_config('host', config)
        self.set_single_config('user', config)
        self.set_single_config('password', config)
        self.set_single_config('db', config)
        self.set_single_config('charset', config)
        self.set_single_config('port', config)

    def get_config(self):
        return {
            'host': self.host,
            'user': self.user,
            'password': self.password,
            'db': self.db,
            'charset': self.charset,
            'port': self.port
        }

    def set_single_config(self, name, config):
        if name in config:
            setattr(self, name, config[name])

    def get_table_names(self):
        tables = self.select('show tables')
        result = []
        for t in tables:
            for name in t.values():
                result.append(name)
        return result

    @property
    def connection(self):
        th = current_thread()
        if th in self._conns:
            result = self._conns[th]
        else:
            result = pymysql.connect(host=self.host,
                                     port=self.port,
                                     user=self.user,
                                     password=self.password,
                                     db=self.db,
                                     charset=self.charset)
            result.autocommit(False)
            with self._conn_lock:
                self._conns[th] = result
        return result

    def select(self, sql, column_name:bool=True):
        logging.getLogger().debug(sql)
        with self:
            with self.connection.cursor() as cursor:
                cursor.execute(sql)
                result = cursor.fetchall()
                if column_name:
                    col_names = [e[0] for e in cursor.description]
                    return [{name: value for name, value in zip(col_names, row)} for row in result]
                return result

    def select_single(self, sql):
        logging.getLogger().debug(sql)
        with self:
            with self.connection.cursor() as cursor:
                cursor.execute(sql)
                result = cursor.fetchone()
                return None if result is None else result[0]

    def select_one(self, sql):
        logging.getLogger().debug(sql)
        with self:
            with self.connection.cursor() as cursor:
                cursor.execute(sql)
                result = cursor.fetchone()
                if result is None:
                    return None
                return {col[0]: value for col, value in zip(cursor.description, result)}

    def execute(self, sql):
        logging.getLogger().debug(sql)
        with self:
            with self.connection.cursor() as cursor:
                return cursor.execute(sql)

    def insert(self, sql, return_id=True):
        logging.getLogger().debug(sql)
        with self:
            with self.connection.cursor() as cursor:
                cursor.execute(sql)
                if return_id:
                    return cursor.lastrowid

    def __enter__(self):
        th = current_thread()
        if th in self._depth:
            self._depth[th] += 1
        else:
            with self._depth_lock:
                self._depth[th] = 1
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        th = current_thread()
        depth = self._depth[th] - 1
        if depth == 0:
            with self._depth_lock:
                del self._depth[th]
            if th in self._conns:
                conn = self._conns[th]
                if exc_val is None:
                    conn.commit()
                else:
                    conn.rollback()
                conn.close()
                with self._conn_lock:
                    del self._conns[th]
        else:
            self._depth[th] = depth
